🎓 Maestría en Inteligencia Artificial Aplicada¶
🖼️ Visión Computacional para Imágenes y Video¶
👨🏫 Profesores¶
- Profesor Titular: Dr. Gilberto Ochoa Ruiz
- Profesor Asistente: MIP Ma. del Refugio Melendez Alfaro
- Profesor Tutor: M. en C. Jose Angel Martinez Navarro
Actividad 7.2: Algoritmos de Extracción de Características - Feature Matching¶
📌 Detalles de la Actividad¶
- Código: 7.2 Google Colab
- Título: Extracción de características y Feature Matching
- Fecha de entrega: 📅 Octubre 26, 2025 a las 23:59
- Formato de entrega: ZIP
- Modalidad: Equipo
👥 Team 13¶
🚀 Nuestro Equipo¶
Javier Augusto Rebull Saucedo¶
Matrícula: |
Juan Carlos Pérez Nava¶
Matrícula: |
Luis Gerardo Sánchez Salazar¶
Matrícula: |
Oscar Enrique García García¶
Matrícula: |
Objetivo del Proyecto¶
Los algoritmos de extracción de características son fundamentales en visión computacional moderna, siendo componentes esenciales para:
- Detección y matching de puntos de interés entre imágenes
- Creación de panoramas mediante image stitching
- Rastreo de objetos en robótica y sistemas autónomos
- Reconstrucción 3D y Structure from Motion (SfM)
- Reconocimiento visual y navegación de drones
- Realidad aumentada y tracking en tiempo real
En esta actividad implementaremos y compararemos tres algoritmos fundamentales de feature detection y matching:
🔹 Harris Corner Detector - Detección de esquinas basada en cambios de intensidad
🔹 SIFT (Scale Invariant Feature Transform) - Descriptor robusto invariante a escala y rotación
🔹 ORB (Oriented FAST and Rotated BRIEF) - Alternativa eficiente y libre de patentes
Evaluaremos el desempeño de estos métodos bajo diferentes condiciones: cambios de iluminación, rotación, escala y oclusión parcial. Aplicaremos principios de Clean Code para generar código mantenible, escalable y profesional.
📚 Tabla de Contenidos¶
- Configuración Inicial e Importación de Librerías
- Descarga y Preparación de Imágenes
- 2.1 Descarga automática con gdown
- 2.2 Funciones de utilidad (resize, conversión a escala de grises)
- ORB (Oriented FAST and Rotated BRIEF)
- 3.1 Introducción a ORB
- 3.2 Carga de imagen demo
- Creación de Imagen de Prueba con Invarianza
- 4.1 Transformaciones de escala (pirámide gaussiana)
- 4.2 Transformaciones de rotación
- Visualización de Imágenes de Entrenamiento y Prueba
- Detección de Keypoints con ORB
- 6.1 Extracción de keypoints y descriptores
- 6.2 Visualización con y sin tamaño
- 6.3 Matching con Brute Force Matcher
- SIFT Matching (Scale Invariant Feature Transform)
- 7.1 Introducción a SIFT
- 7.2 Detección de keypoints en imagen única
- Matching entre Diferentes Imágenes
- 8.1 Carga de par de imágenes (Elon Musk)
- Extracción de Keypoints con SIFT
- 9.1 Detección en ambas imágenes
- Feature Matching Comparativo
- 10.1 Matching con norma L2
- 10.2 Visualización de top matches
- Análisis Comparativo de Algoritmos
- 11.1 Framework de comparación (Clase FeatureMatchingComparator)
- 11.2 Harris Corner Detection + ORB Descriptors
- 11.3 Comparación Demo: Elon Musk
- 11.4 Comparaciones Wall-E (múltiples escenarios)
- 11.5 Comparación Puzzle de Popeye
- 11.6 Visualización de resultados agregados
- 11.7 Tabla comparativa detallada
- 11.8 Estadísticas agregadas
- 11.9 Hallazgos y análisis de resultados
- Conclusiones Personales
- 12.1 Javier Augusto Rebull Saucedo
- 12.2 Juan Carlos Pérez Nava
- 12.3 Luis Gerardo Sánchez Salazar
- 12.4 Oscar Enrique García García
- Reflexión Final del Equipo
- 13.1 Logros destacados
- 13.2 Aprendizajes clave
- 13.3 Aplicaciones potenciales
- 13.4 Referencias bibliográficas
🔬 Metodología de Análisis¶
Para garantizar una evaluación rigurosa y científica, seguiremos esta metodología:
- Detección de Keypoints: Identificar puntos distintivos en cada imagen
- Computación de Descriptores: Generar vectores de características para cada keypoint
- Matching: Encontrar correspondencias entre descriptores de dos imágenes
- Evaluación Cuantitativa: Métricas de distancia promedio, número de matches
- Evaluación Cualitativa: Análisis visual de la calidad de los matches
- Comparación Multi-Escenario: Probar bajo rotaciones, escalas y oclusiones
💻 Tecnologías Utilizadas¶
- Python 3.8+ - Lenguaje de programación
- OpenCV 4.x - Biblioteca de visión computacional
- NumPy - Computación numérica
- Matplotlib / Seaborn - Visualización de datos
- Google Colab - Entorno de ejecución
- gdown - Descarga de datasets desde Google Drive
🚀 ¡Comencemos con el análisis comparativo de feature matching!
📖 Fundamentos Teóricos: Feature Detection & Matching¶
🎯 El Problema Fundamental¶
El desafío central en visión computacional es encontrar correspondencias entre imágenes que muestran el mismo objeto o escena bajo diferentes condiciones [1]. Las imágenes pueden sufrir transformaciones que dificultan el matching:
- Transformaciones Geométricas: Traslación, rotación, cambios de escala, perspectiva [2]
- Transformaciones Fotométricas: Variaciones de iluminación, contraste, exposición [2]
Resolver este problema es crucial para aplicaciones como reconstrucción 3D, SLAM (Simultaneous Localization and Mapping), image stitching y realidad aumentada [3].
🔄 Pipeline de Feature Matching¶
El proceso se divide en tres etapas fundamentales [4]:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Detection │ ───▶ │ Description │ ───▶ │ Matching │
│ (Keypoints)│ │ (Descriptors)│ │ (Correspondence)│
└─────────────┘ └──────────────┘ └─────────────┘
- Detección (Detection): Identificar puntos de interés distintivos en la imagen
- Descripción (Description): Computar un vector descriptor para cada keypoint
- Correspondencia (Matching): Comparar descriptores para encontrar matches
✨ Propiedades Deseadas de Features¶
Un buen detector/descriptor debe cumplir [5]:
| Propiedad | Descripción |
|---|---|
| Repetibilidad | El mismo punto debe detectarse en múltiples vistas |
| Invarianza a Escala | Robusto ante cambios de tamaño del objeto |
| Invarianza a Rotación | Robusto ante rotaciones de la imagen |
| Invarianza a Iluminación | Robusto ante cambios de brillo/contraste |
| Prominencia | Detectar regiones distintivas y salientes |
| Localización | Features bien localizados espacialmente |
🔍 Algoritmos Implementados¶
1. Harris Corner Detector (Harris & Stephens, 1988) [6]¶
Concepto: Detecta esquinas mediante análisis de cambios de intensidad en múltiples direcciones.
Matemática: Función de "cornerness"
R = det(M) - k·trace(M)²
Donde M es la matriz de estructura del tensor de segundo momento.
Características:
- ✅ Excelente localización de esquinas
- ✅ Invariante a traslación y rotación
- ❌ NO invariante a escala
- 📊 Usado como detector, requiere descriptores adicionales (ej. ORB)
2. SIFT - Scale Invariant Feature Transform (Lowe, 2004) [7]¶
Concepto: Detector y descriptor robusto basado en extremos de Difference of Gaussians (DoG) en espacio de escalas.
Pipeline SIFT:
- Detección: Extremos locales en DoG (aproximación eficiente del Laplacian of Gaussian)
- Localización: Refinamiento sub-pixel de keypoints
- Orientación: Asignación de orientación dominante (invarianza a rotación)
- Descriptor: Histogramas de gradientes orientados en región 16×16 → vector 128D
Características:
- ✅ Altamente robusto a escala, rotación, iluminación
- ✅ Descriptor de 128 dimensiones muy distintivo
- ✅ Tolerante a cambios de viewpoint hasta 30° [8]
- ❌ Computacionalmente costoso
- ⚖️ Patentado (expiró en 2020)
Descriptor SIFT: Región dividida en cuadrícula 4×4, cada celda con histograma de 8 orientaciones → 4×4×8 = 128 dimensiones
3. ORB - Oriented FAST and Rotated BRIEF (Rublee et al., 2011) [9]¶
Concepto: Alternativa rápida y libre a SIFT/SURF, combina detector FAST con descriptor BRIEF modificado.
Componentes:
- Detector: FAST (Features from Accelerated Segment Test) con medida de Harris
- Descriptor: BRIEF (Binary Robust Independent Elementary Features) con orientación
- Orientación: Basada en intensity centroid para invarianza a rotación
Características:
- ✅ Muy rápido (2 órdenes de magnitud más rápido que SIFT)
- ✅ Descriptor binario → matching eficiente (distancia de Hamming)
- ✅ Libre de patentes y open-source
- ❌ Menos robusto que SIFT ante transformaciones extremas
- 🚀 Ideal para aplicaciones en tiempo real (robótica móvil, drones)
Descriptor ORB: Vector binario de 256 bits (vs 128 floats de SIFT)
🔗 Feature Matching¶
Una vez extraídos los descriptores, se realiza el matching mediante:
Brute Force Matcher:
- SIFT: Norma L2 (distancia Euclidiana) para descriptores de punto flotante
- ORB/Harris: Norma de Hamming para descriptores binarios
Ratio Test (Lowe's Ratio Test) [7]:
ratio = ||descriptor1 - best_match|| / ||descriptor1 - second_best_match||
if ratio < 0.75: # threshold típico
accept_match()
Este test filtra matches ambiguos, mejorando la precisión del matching.
📊 Comparación Rápida¶
| Algoritmo | Invarianza Escala | Invarianza Rotación | Velocidad | Descriptor |
|---|---|---|---|---|
| Harris | ❌ | ✅ | ⚡⚡⚡ | Requiere externo |
| SIFT | ✅ | ✅ | 🐢 | 128D float |
| ORB | Limitada | ✅ | ⚡⚡⚡ | 256 bits |
📚 Referencias¶
[1] Ochoa Ruiz, G. (2024). Módulo 3.2 - Extracción de Descriptores. Visión Computacional para Imágenes y Video, Tec de Monterrey.
[2] Szeliski, R. (2022). Computer Vision: Algorithms and Applications (2nd ed.). Springer.
[3] Hartley, R., & Zisserman, A. (2004). Multiple View Geometry in Computer Vision (2nd ed.). Cambridge University Press.
[4] Tuytelaars, T., & Mikolajczyk, K. (2008). Local Invariant Feature Detectors: A Survey. Foundations and Trends in Computer Graphics and Vision, 3(3), 177-280.
[5] Schmid, C., Mohr, R., & Bauckhage, C. (2000). Evaluation of Interest Point Detectors. International Journal of Computer Vision, 37(2), 151-172.
[6] Harris, C., & Stephens, M. (1988). A Combined Corner and Edge Detector. Alvey Vision Conference, 147-151.
[7] Lowe, D. G. (2004). Distinctive Image Features from Scale-Invariant Keypoints. International Journal of Computer Vision, 60(2), 91-110. DOI: 10.1023/B:VISI.0000029664.99615.94
[8] Mikolajczyk, K., & Schmid, C. (2005). A Performance Evaluation of Local Descriptors. IEEE Transactions on Pattern Analysis and Machine Intelligence, 27(10), 1615-1630.
[9] Rublee, E., Rabaud, V., Konolige, K., & Bradski, G. (2011). ORB: An Efficient Alternative to SIFT or SURF. IEEE International Conference on Computer Vision (ICCV), 2564-2571.
[10] OpenCV Documentation. (2024). Feature Detection and Description. Retrieved from https://docs.opencv.org/4.x/db/d27/tutorial_py_table_of_contents_feature2d.html
[11] Solem, J. E. (2012). Programming Computer Vision with Python. O'Reilly Media.
💡 Nota: Esta actividad se enfoca en implementación práctica y análisis comparativo empírico de estos algoritmos bajo diferentes condiciones de transformación.
📑 Tabla de Contenidos¶
- Importación de Librerías
- Descarga y Preparación de Imágenes
- ORB (Oriented FAST and Rotated BRIEF)
- Creación de Imagen de Prueba con Invarianza
- Visualización de Imágenes de Entrenamiento y Prueba
- Detección de Keypoints con ORB
- SIFT Matching (Scale Invariant Feature Transform)
- Matching entre Diferentes Imágenes
- Extracción de Keypoints con SIFT
- Feature Matching Comparativo
- Análisis Comparativo de Algoritmos
- Conclusiones Personales
- Reflexión Final del Equipo
1. Importación de Librerías ¶
Importamos las bibliotecas fundamentales para procesamiento de imágenes, detección de características y visualización de resultados.
# Librerías para procesamiento de imágenes y visión computacional
import cv2
import numpy as np
# Librerías para visualización
import matplotlib.pyplot as plt
import seaborn as sns
# Librerías para descarga de archivos
import gdown
import os
from pathlib import Path
# Configuración de visualización
plt.style.use('seaborn-v0_8-darkgrid')
sns.set_palette("husl")
%matplotlib inline
print("✅ Librerías importadas correctamente")
print(f"OpenCV version: {cv2.__version__}")
✅ Librerías importadas correctamente OpenCV version: 4.12.0
2. Descarga y Preparación de Imágenes ¶
Descargamos las imágenes desde Google Drive usando gdown y las preparamos para el procesamiento. Incluye funciones genéricas para redimensionar y convertir a escala de grises.
# Diccionario con las imágenes y sus IDs de Google Drive
IMAGE_DATASET = {
'01_Wall-E_Front_Environment1.jpg': '1qXaPcFsA9w4ce7fVvXkvauFiOKPrOiB8',
'02_Wall-E_Right_Side_Environment1.jpg': '1O7v9FJOuynI1gW28FR3mJawKMxHjaIg2',
'03_Wall-E_Right_Side_Environment2.jpg': '1aNybti8wTIswStoUcvDaf6ss6KhTljh7',
'04_Wall-E_Left_Side_Environment2.jpg': '1ALYDEu_36moDBnAuOONAuXCflc8pUUKK',
'05_Wall-E_Zoom_Out_Up_Environment2.jpg': '1Wzrj4FhvWGt9xI7kymqG08G2jmJCc_rO',
'06_Wall-E_Close_Up_Front_Environment2.jpg': '1BDq3VTfukq1CeyS-GgwhgTTvR4-78TtV',
'07_Wall-E_Laydown_Environment2.jpg': '1gwoIszT95veji_ITv5O_Mk9wAgUOFUw5',
'08_Wall-E_Upside_Down_Environment2.jpg': '1sPM-ZGM0PZONefeXtYysR3WvxJLuO-O4',
'09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg': '1VKJWf6KoA2SPv_T5qFPJzGry5RlruPqz',
'10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg': '1SMEbXT9bLlzONqgT7IKU7J096dFnA2D2',
'11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg': '1ApvBwr8ox-IJAnGnQ_vpSCBfeJAdZCTi',
'12_Wall-E_Laydown_withObjects_Environment1.jpg': '1vOkPFIsVJDhWtkXZlzneLU6-sn4u1aHj',
'13_Wall-E_Front_Outside_Environment3.jpg': '1FRlGdGAJn26Wg5w5Qfx-ZsBO2ztsASxZ',
'14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg': '1dd8tAPz8Jqe_L0Liyyko5HRhewypJWb_',
'15_PopeyePuzzle_Front.jpg': '1tjEmHfm3UZmIkt7blOgGcsJ-AyKjmUfk',
'16_PopeyePuzzle_MorePuzzles.jpg': '1Dq2s1kKf244yRqTPr7ascnTo8LQ_Vj_C',
'elon_1.jpg': '1IQegnvUTqsOo0BFlwrRZ0LA9vgoA5NY1',
'elon_2.png': '1MkG74Tzwl8BTUw4f3C7jZTnjB0uVFc3R'
}
# Crear directorio para las imágenes
DATA_DIR = Path('data')
DATA_DIR.mkdir(exist_ok=True)
def download_images(image_dict: dict, output_dir: Path) -> None:
"""
Descarga imágenes desde Google Drive usando gdown.
Args:
image_dict: Diccionario con nombres de archivo como keys e IDs de Drive como values
output_dir: Directorio donde se guardarán las imágenes
"""
for filename, file_id in image_dict.items():
output_path = output_dir / filename
if output_path.exists():
print(f"⏭️ {filename} ya existe, omitiendo descarga")
continue
url = f'https://drive.google.com/uc?id={file_id}'
print(f"📥 Descargando {filename}...")
gdown.download(url, str(output_path), quiet=False)
print("\n✅ Todas las imágenes descargadas correctamente")
# Descargar imágenes
download_images(IMAGE_DATASET, DATA_DIR)
📥 Descargando 01_Wall-E_Front_Environment1.jpg...
Downloading... From: https://drive.google.com/uc?id=1qXaPcFsA9w4ce7fVvXkvauFiOKPrOiB8 To: /content/data/01_Wall-E_Front_Environment1.jpg 100%|██████████| 331k/331k [00:00<00:00, 35.5MB/s]
📥 Descargando 02_Wall-E_Right_Side_Environment1.jpg...
Downloading... From: https://drive.google.com/uc?id=1O7v9FJOuynI1gW28FR3mJawKMxHjaIg2 To: /content/data/02_Wall-E_Right_Side_Environment1.jpg 100%|██████████| 306k/306k [00:00<00:00, 8.43MB/s]
📥 Descargando 03_Wall-E_Right_Side_Environment2.jpg...
Downloading... From: https://drive.google.com/uc?id=1aNybti8wTIswStoUcvDaf6ss6KhTljh7 To: /content/data/03_Wall-E_Right_Side_Environment2.jpg 100%|██████████| 500k/500k [00:00<00:00, 14.1MB/s]
📥 Descargando 04_Wall-E_Left_Side_Environment2.jpg...
Downloading... From: https://drive.google.com/uc?id=1ALYDEu_36moDBnAuOONAuXCflc8pUUKK To: /content/data/04_Wall-E_Left_Side_Environment2.jpg 100%|██████████| 500k/500k [00:00<00:00, 9.01MB/s]
📥 Descargando 05_Wall-E_Zoom_Out_Up_Environment2.jpg...
Downloading... From: https://drive.google.com/uc?id=1Wzrj4FhvWGt9xI7kymqG08G2jmJCc_rO To: /content/data/05_Wall-E_Zoom_Out_Up_Environment2.jpg 100%|██████████| 696k/696k [00:00<00:00, 30.7MB/s]
📥 Descargando 06_Wall-E_Close_Up_Front_Environment2.jpg...
Downloading... From: https://drive.google.com/uc?id=1BDq3VTfukq1CeyS-GgwhgTTvR4-78TtV To: /content/data/06_Wall-E_Close_Up_Front_Environment2.jpg 100%|██████████| 451k/451k [00:00<00:00, 12.6MB/s]
📥 Descargando 07_Wall-E_Laydown_Environment2.jpg...
Downloading... From: https://drive.google.com/uc?id=1gwoIszT95veji_ITv5O_Mk9wAgUOFUw5 To: /content/data/07_Wall-E_Laydown_Environment2.jpg 100%|██████████| 638k/638k [00:00<00:00, 15.0MB/s]
📥 Descargando 08_Wall-E_Upside_Down_Environment2.jpg...
Downloading... From: https://drive.google.com/uc?id=1sPM-ZGM0PZONefeXtYysR3WvxJLuO-O4 To: /content/data/08_Wall-E_Upside_Down_Environment2.jpg 100%|██████████| 438k/438k [00:00<00:00, 9.58MB/s]
📥 Descargando 09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg...
Downloading... From: https://drive.google.com/uc?id=1VKJWf6KoA2SPv_T5qFPJzGry5RlruPqz To: /content/data/09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg 100%|██████████| 315k/315k [00:00<00:00, 6.91MB/s]
📥 Descargando 10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg...
Downloading... From: https://drive.google.com/uc?id=1SMEbXT9bLlzONqgT7IKU7J096dFnA2D2 To: /content/data/10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg 100%|██████████| 388k/388k [00:00<00:00, 11.7MB/s]
📥 Descargando 11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg...
Downloading... From: https://drive.google.com/uc?id=1ApvBwr8ox-IJAnGnQ_vpSCBfeJAdZCTi To: /content/data/11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg 100%|██████████| 333k/333k [00:00<00:00, 16.4MB/s]
📥 Descargando 12_Wall-E_Laydown_withObjects_Environment1.jpg...
Downloading... From: https://drive.google.com/uc?id=1vOkPFIsVJDhWtkXZlzneLU6-sn4u1aHj To: /content/data/12_Wall-E_Laydown_withObjects_Environment1.jpg 100%|██████████| 610k/610k [00:00<00:00, 11.7MB/s]
📥 Descargando 13_Wall-E_Front_Outside_Environment3.jpg...
Downloading... From: https://drive.google.com/uc?id=1FRlGdGAJn26Wg5w5Qfx-ZsBO2ztsASxZ To: /content/data/13_Wall-E_Front_Outside_Environment3.jpg 100%|██████████| 1.05M/1.05M [00:00<00:00, 39.0MB/s]
📥 Descargando 14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg...
Downloading... From: https://drive.google.com/uc?id=1dd8tAPz8Jqe_L0Liyyko5HRhewypJWb_ To: /content/data/14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg 100%|██████████| 457k/457k [00:00<00:00, 12.7MB/s]
📥 Descargando 15_PopeyePuzzle_Front.jpg...
Downloading... From: https://drive.google.com/uc?id=1tjEmHfm3UZmIkt7blOgGcsJ-AyKjmUfk To: /content/data/15_PopeyePuzzle_Front.jpg 100%|██████████| 433k/433k [00:00<00:00, 12.0MB/s]
📥 Descargando 16_PopeyePuzzle_MorePuzzles.jpg...
Downloading... From: https://drive.google.com/uc?id=1Dq2s1kKf244yRqTPr7ascnTo8LQ_Vj_C To: /content/data/16_PopeyePuzzle_MorePuzzles.jpg 100%|██████████| 774k/774k [00:00<00:00, 13.0MB/s]
📥 Descargando elon_1.jpg...
Downloading... From: https://drive.google.com/uc?id=1IQegnvUTqsOo0BFlwrRZ0LA9vgoA5NY1 To: /content/data/elon_1.jpg 100%|██████████| 28.7k/28.7k [00:00<00:00, 29.7MB/s]
📥 Descargando elon_2.png...
Downloading... From: https://drive.google.com/uc?id=1MkG74Tzwl8BTUw4f3C7jZTnjB0uVFc3R To: /content/data/elon_2.png 100%|██████████| 280k/280k [00:00<00:00, 10.5MB/s]
✅ Todas las imágenes descargadas correctamente
def load_and_preprocess_image(image_path: str, max_width: int = 800,
convert_to_gray: bool = True) -> tuple:
"""
Carga y preprocesa una imagen: redimensiona y convierte a escala de grises.
Args:
image_path: Ruta de la imagen
max_width: Ancho máximo para redimensionar
convert_to_gray: Si True, convierte a escala de grises
Returns:
Tupla (imagen_color, imagen_gris) o (imagen_color, None) si convert_to_gray=False
"""
# Leer imagen
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError(f"No se pudo cargar la imagen: {image_path}")
# Redimensionar si es necesario
height, width = img.shape[:2]
if width > max_width:
scale = max_width / width
new_width = max_width
new_height = int(height * scale)
img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA)
# Convertir BGR a RGB para visualización correcta
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Convertir a escala de grises si se requiere
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if convert_to_gray else None
return img_rgb, img_gray
def display_image_pair(img1: np.ndarray, img2: np.ndarray,
title1: str = "Imagen 1", title2: str = "Imagen 2",
figsize: tuple = (16, 6), cmap: str = None) -> None:
"""
Visualiza dos imágenes lado a lado.
Args:
img1, img2: Imágenes a visualizar
title1, title2: Títulos de las imágenes
figsize: Tamaño de la figura
cmap: Mapa de colores (ej. 'gray' para escala de grises)
"""
fig, axes = plt.subplots(1, 2, figsize=figsize)
axes[0].imshow(img1, cmap=cmap)
axes[0].set_title(title1, fontsize=12, fontweight='bold')
axes[0].axis('off')
axes[1].imshow(img2, cmap=cmap)
axes[1].set_title(title2, fontsize=12, fontweight='bold')
axes[1].axis('off')
plt.tight_layout()
plt.show()
print("✅ Funciones de utilidad definidas")
✅ Funciones de utilidad definidas
3. ORB (Oriented FAST and Rotated BRIEF) ¶
ORB fue desarrollado en OpenCV Labs (2011) como una alternativa eficiente y libre a SIFT y SURF. Combina el detector de keypoints FAST con el descriptor binario BRIEF, añadiendo invarianza a la rotación y mejorando la robustez.
# Cargar imagen de demostración
img_color, img_gray = load_and_preprocess_image('data/elon_1.jpg')
# Visualizar imagen original
display_image_pair(img_color, img_gray,
title1="Imagen Original (Color)",
title2="Imagen en Escala de Grises",
cmap='gray')
4. Creación de Imagen de Prueba con Invarianza ¶
Para evaluar la robustez de los descriptores, creamos una imagen de prueba aplicando transformaciones de escala (pirámide gaussiana) y rotación. Esto permite verificar la invarianza de los algoritmos ante estas transformaciones geométricas.
def create_transformed_image(img: np.ndarray, scale_levels: int = 2,
rotation_angle: float = 30) -> tuple:
"""
Crea una versión transformada de la imagen con cambios de escala y rotación.
Args:
img: Imagen original
scale_levels: Número de niveles de pirámide (escala)
rotation_angle: Ángulo de rotación en grados
Returns:
Tupla (imagen_transformada_color, imagen_transformada_gray)
"""
# Aplicar reducción de escala mediante pirámide gaussiana
transformed = img.copy()
for _ in range(scale_levels):
transformed = cv2.pyrDown(transformed)
# Aplicar rotación
num_rows, num_cols = transformed.shape[:2]
center = (num_cols / 2, num_rows / 2)
rotation_matrix = cv2.getRotationMatrix2D(center, rotation_angle, 1.0)
transformed = cv2.warpAffine(transformed, rotation_matrix, (num_cols, num_rows))
# Convertir a escala de grises
transformed_gray = cv2.cvtColor(transformed, cv2.COLOR_RGB2GRAY)
return transformed, transformed_gray
# Crear imagen de prueba con transformaciones
test_img_color, test_img_gray = create_transformed_image(img_color,
scale_levels=2,
rotation_angle=30)
print(f"✅ Imagen transformada creada")
print(f" - Escala reducida: 2 niveles de pirámide")
print(f" - Rotación aplicada: 30 grados")
print(f" - Tamaño original: {img_color.shape[:2]}")
print(f" - Tamaño transformado: {test_img_color.shape[:2]}")
✅ Imagen transformada creada - Escala reducida: 2 niveles de pirámide - Rotación aplicada: 30 grados - Tamaño original: (357, 632) - Tamaño transformado: (90, 158)
5. Visualización de Imágenes de Entrenamiento y Prueba ¶
Comparamos visualmente la imagen original (entrenamiento) con la imagen transformada (prueba).
display_image_pair(img_color, test_img_color,
title1="Imagen de Entrenamiento (Original)",
title2="Imagen de Prueba (Escalada + Rotada)",
figsize=(18, 7))
6. Detección de Keypoints con ORB ¶
Detectamos keypoints y computamos descriptores usando ORB. Visualizamos los keypoints detectados con y sin información de tamaño y orientación.
# Crear detector ORB
orb = cv2.ORB_create(nfeatures=1000)
# Detectar keypoints y computar descriptores
train_keypoints, train_descriptors = orb.detectAndCompute(img_color, None)
test_keypoints, test_descriptors = orb.detectAndCompute(test_img_gray, None)
print(f"🔍 Keypoints detectados:")
print(f" - Imagen de entrenamiento: {len(train_keypoints)}")
print(f" - Imagen de prueba: {len(test_keypoints)}")
# Visualizar keypoints
img_keypoints_simple = cv2.drawKeypoints(img_color, train_keypoints, None,
color=(0, 255, 0))
img_keypoints_rich = cv2.drawKeypoints(img_color, train_keypoints, None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
display_image_pair(img_keypoints_rich, img_keypoints_simple,
title1="Keypoints con Tamaño y Orientación",
title2="Keypoints Simples",
figsize=(18, 7))
🔍 Keypoints detectados: - Imagen de entrenamiento: 960 - Imagen de prueba: 39
Matching con ORB¶
Utilizamos Brute Force Matcher con distancia de Hamming (apropiada para descriptores binarios) para encontrar correspondencias entre keypoints.
# Crear Brute Force Matcher para descriptores binarios
bf_matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# Realizar matching
matches = bf_matcher.match(train_descriptors, test_descriptors)
matches = sorted(matches, key=lambda x: x.distance)
# Visualizar mejores matches
num_matches_to_show = min(50, len(matches))
img_matches = cv2.drawMatches(img_color, train_keypoints,
test_img_gray, test_keypoints,
matches[:num_matches_to_show], None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.figure(figsize=(18, 8))
plt.title(f'ORB - Mejores {num_matches_to_show} Matches', fontsize=14, fontweight='bold')
plt.imshow(img_matches)
plt.axis('off')
plt.tight_layout()
plt.show()
print(f"\n📊 Estadísticas de Matching ORB:")
print(f" - Total de matches encontrados: {len(matches)}")
print(f" - Distancia promedio (top 50): {np.mean([m.distance for m in matches[:50]]):.2f}")
📊 Estadísticas de Matching ORB: - Total de matches encontrados: 30 - Distancia promedio (top 50): 54.07
7. SIFT Matching (Scale Invariant Feature Transform) ¶
SIFT es uno de los algoritmos más robustos para detección de características, ofreciendo invarianza a escala, rotación, iluminación y cambios moderados de perspectiva. Utiliza Difference of Gaussians (DoG) para detección y histogramas de gradientes orientados para descripción.
# Cargar imagen y convertir a escala de grises
img_sift, gray_sift = load_and_preprocess_image('data/elon_1.jpg')
# Crear detector SIFT
sift = cv2.SIFT_create()
# Detectar keypoints y descriptores
keypoints_sift, descriptors_sift = sift.detectAndCompute(img_sift, None)
# Dibujar keypoints
img_with_keypoints = cv2.drawKeypoints(gray_sift, keypoints_sift, img_sift,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
plt.figure(figsize=(14, 8))
plt.title(f'SIFT Keypoints Detectados: {len(keypoints_sift)}',
fontsize=14, fontweight='bold')
plt.imshow(img_with_keypoints)
plt.axis('off')
plt.tight_layout()
plt.show()
print(f"🔍 Keypoints SIFT detectados: {len(keypoints_sift)}")
🔍 Keypoints SIFT detectados: 293
8. Matching entre Diferentes Imágenes ¶
Evaluamos el matching entre dos imágenes diferentes de la misma persona con variaciones en pose e iluminación.
# Cargar par de imágenes
img1_color, img1_gray = load_and_preprocess_image('data/elon_1.jpg')
img2_color, img2_gray = load_and_preprocess_image('data/elon_2.png')
# Visualizar ambas imágenes
display_image_pair(img1_gray, img2_gray,
title1="Imagen 1 - Elon Musk",
title2="Imagen 2 - Elon Musk (Diferente Pose)",
cmap='gray')
9. Extracción de Keypoints con SIFT ¶
Extraemos keypoints y descriptores para ambas imágenes usando SIFT.
# Detectar keypoints y descriptores en ambas imágenes
sift = cv2.SIFT_create()
kp1, desc1 = sift.detectAndCompute(img1_gray, None)
kp2, desc2 = sift.detectAndCompute(img2_gray, None)
print(f"🔍 Keypoints extraídos:")
print(f" - Imagen 1: {len(kp1)} keypoints")
print(f" - Imagen 2: {len(kp2)} keypoints")
🔍 Keypoints extraídos: - Imagen 1: 296 keypoints - Imagen 2: 390 keypoints
10. Feature Matching Comparativo ¶
Realizamos matching usando Brute Force Matcher con norma L2 (apropiada para descriptores SIFT de punto flotante).
# Crear matcher con norma L2
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
# Realizar matching
matches = bf.match(desc1, desc2)
matches = sorted(matches, key=lambda x: x.distance)
# Visualizar top 50 matches
img_matches = cv2.drawMatches(img1_gray, kp1, img2_gray, kp2,
matches[:50], None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.figure(figsize=(18, 8))
plt.title('SIFT - Top 50 Matches', fontsize=14, fontweight='bold')
plt.imshow(img_matches, cmap='gray')
plt.axis('off')
plt.tight_layout()
plt.show()
print(f"\n📊 Total de matches encontrados: {len(matches)}")
📊 Total de matches encontrados: 102
11. Análisis Comparativo de Algoritmos ¶
Implementamos un framework completo para comparar Harris + ORB, SIFT y ORB en múltiples pares de imágenes. Evaluamos la calidad del matching mediante la distancia promedio de los matches.
Harris Corner Detection¶
Harris detecta esquinas (regiones con cambios de intensidad significativos en múltiples direcciones). Aunque no es invariante a escala, proporciona puntos muy estables y repetibles. Lo combinamos con descriptores ORB para el matching.
class FeatureMatchingComparator:
"""
Clase para comparar algoritmos de feature matching.
Implementa Harris, SIFT y ORB con métricas de evaluación.
"""
def __init__(self):
self.metrics = {
'image_pairs': [],
'sift_50': [], 'sift_all': [],
'orb_50': [], 'orb_all': [],
'harris_50': [], 'harris_all': [],
'keypoints_sift': [], 'keypoints_orb': [], 'keypoints_harris': [],
'matches_sift': [], 'matches_orb': [], 'matches_harris': []
}
self.orb = cv2.ORB_create(nfeatures=1000)
self.sift = cv2.SIFT_create()
def _extract_harris_keypoints(self, img_gray: np.ndarray) -> list:
"""
Extrae keypoints usando Harris Corner Detector.
Args:
img_gray: Imagen en escala de grises
Returns:
Lista de cv2.KeyPoint objetos
"""
harris_corners = cv2.cornerHarris(img_gray, blockSize=2, ksize=3, k=0.04)
harris_corners = cv2.dilate(harris_corners, None)
# Umbral para seleccionar esquinas prominentes
threshold = 0.01 * harris_corners.max()
keypoint_coords = np.argwhere(harris_corners > threshold)
# Convertir coordenadas a KeyPoint objects
keypoints = [cv2.KeyPoint(float(pt[1]), float(pt[0]), 1)
for pt in keypoint_coords]
return keypoints
def _compute_average_distance(self, matches: list, num_matches: int) -> float:
"""
Calcula distancia promedio de los primeros N matches.
Args:
matches: Lista de DMatch objects
num_matches: Número de matches a considerar
Returns:
Distancia promedio
"""
if not matches or num_matches <= 0:
return 0.0
subset = matches[:min(num_matches, len(matches))]
return sum(m.distance for m in subset) / len(subset)
def _format_image_pair_name(self, path1: str, path2: str) -> str:
"""
Formatea nombres de archivos para etiquetas de gráficos.
"""
name1 = Path(path1).stem[:15]
name2 = Path(path2).stem[:15]
return f"{name1}\nvs\n{name2}"
def _plot_matches(self, title: str, img1: np.ndarray, kp1: list,
img2: np.ndarray, kp2: list, matches: list,
num_matches: int) -> None:
"""
Visualiza matches entre dos imágenes.
"""
matches_to_draw = matches[:min(num_matches, len(matches))]
img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches_to_draw, None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.figure(figsize=(18, 6))
plt.title(f'{title} - {len(matches_to_draw)} Matches',
fontsize=13, fontweight='bold')
plt.imshow(img_matches, cmap='gray')
plt.axis('off')
plt.tight_layout()
plt.show()
def compare_algorithms(self, img1_path: str, img2_path: str,
visualize: bool = True) -> dict:
"""
Compara Harris, SIFT y ORB en un par de imágenes.
Args:
img1_path: Ruta de primera imagen
img2_path: Ruta de segunda imagen
visualize: Si True, muestra visualizaciones
Returns:
Diccionario con métricas del par de imágenes
"""
# Cargar imágenes
_, img1_gray = load_and_preprocess_image(img1_path)
_, img2_gray = load_and_preprocess_image(img2_path)
pair_name = self._format_image_pair_name(img1_path, img2_path)
if visualize:
display_image_pair(img1_gray, img2_gray,
title1=Path(img1_path).name,
title2=Path(img2_path).name,
cmap='gray', figsize=(16, 5))
results = {}
# ========== HARRIS + ORB DESCRIPTORS ==========
kp1_harris = self._extract_harris_keypoints(img1_gray)
kp2_harris = self._extract_harris_keypoints(img2_gray)
_, desc1_harris = self.orb.compute(img1_gray, kp1_harris)
_, desc2_harris = self.orb.compute(img2_gray, kp2_harris)
if desc1_harris is not None and desc2_harris is not None:
bf_harris = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches_harris = sorted(bf_harris.match(desc1_harris, desc2_harris),
key=lambda x: x.distance)
results['harris'] = {
'keypoints': (len(kp1_harris), len(kp2_harris)),
'matches': len(matches_harris),
'avg_dist_50': self._compute_average_distance(matches_harris, 50),
'avg_dist_all': self._compute_average_distance(matches_harris, len(matches_harris))
}
if visualize:
self._plot_matches('Harris + ORB Descriptors', img1_gray, kp1_harris,
img2_gray, kp2_harris, matches_harris, 50)
# ========== SIFT ==========
kp1_sift, desc1_sift = self.sift.detectAndCompute(img1_gray, None)
kp2_sift, desc2_sift = self.sift.detectAndCompute(img2_gray, None)
bf_sift = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
matches_sift = sorted(bf_sift.match(desc1_sift, desc2_sift),
key=lambda x: x.distance)
results['sift'] = {
'keypoints': (len(kp1_sift), len(kp2_sift)),
'matches': len(matches_sift),
'avg_dist_50': self._compute_average_distance(matches_sift, 50),
'avg_dist_all': self._compute_average_distance(matches_sift, len(matches_sift))
}
if visualize:
self._plot_matches('SIFT', img1_gray, kp1_sift,
img2_gray, kp2_sift, matches_sift, 50)
# ========== ORB ==========
kp1_orb, desc1_orb = self.orb.detectAndCompute(img1_gray, None)
kp2_orb, desc2_orb = self.orb.detectAndCompute(img2_gray, None)
bf_orb = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches_orb = sorted(bf_orb.match(desc1_orb, desc2_orb),
key=lambda x: x.distance)
results['orb'] = {
'keypoints': (len(kp1_orb), len(kp2_orb)),
'matches': len(matches_orb),
'avg_dist_50': self._compute_average_distance(matches_orb, 50),
'avg_dist_all': self._compute_average_distance(matches_orb, len(matches_orb))
}
if visualize:
self._plot_matches('ORB', img1_gray, kp1_orb,
img2_gray, kp2_orb, matches_orb, 50)
# Almacenar métricas
self.metrics['image_pairs'].append(pair_name)
self.metrics['sift_50'].append(results['sift']['avg_dist_50'])
self.metrics['sift_all'].append(results['sift']['avg_dist_all'])
self.metrics['orb_50'].append(results['orb']['avg_dist_50'])
self.metrics['orb_all'].append(results['orb']['avg_dist_all'])
self.metrics['harris_50'].append(results['harris']['avg_dist_50'])
self.metrics['harris_all'].append(results['harris']['avg_dist_all'])
self.metrics['keypoints_sift'].append(results['sift']['keypoints'][0])
self.metrics['keypoints_orb'].append(results['orb']['keypoints'][0])
self.metrics['keypoints_harris'].append(results['harris']['keypoints'][0])
self.metrics['matches_sift'].append(results['sift']['matches'])
self.metrics['matches_orb'].append(results['orb']['matches'])
self.metrics['matches_harris'].append(results['harris']['matches'])
# Imprimir resultados
print(f"\n{'='*60}")
print(f"📊 Resultados para: {Path(img1_path).name} vs {Path(img2_path).name}")
print(f"{'='*60}")
for algo_name, algo_results in results.items():
print(f"\n🔹 {algo_name.upper()}:")
print(f" Keypoints detectados: Img1={algo_results['keypoints'][0]}, "
f"Img2={algo_results['keypoints'][1]}")
print(f" Total matches: {algo_results['matches']}")
print(f" Distancia promedio (50 mejores): {algo_results['avg_dist_50']:.2f}")
print(f" Distancia promedio (todos): {algo_results['avg_dist_all']:.2f}")
return results
# Crear instancia del comparador
comparator = FeatureMatchingComparator()
print("✅ Clase FeatureMatchingComparator inicializada")
✅ Clase FeatureMatchingComparator inicializada
Comparación Demo: Elon Musk¶
# Comparar imágenes de Elon Musk
results_elon = comparator.compare_algorithms('data/elon_1.jpg', 'data/elon_2.png')
============================================================ 📊 Resultados para: elon_1.jpg vs elon_2.png ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=1125, Img2=1244 Total matches: 152 Distancia promedio (50 mejores): 17.26 Distancia promedio (todos): 28.60 🔹 SIFT: Keypoints detectados: Img1=296, Img2=390 Total matches: 102 Distancia promedio (50 mejores): 204.85 Distancia promedio (todos): 263.11 🔹 ORB: Keypoints detectados: Img1=967, Img2=999 Total matches: 311 Distancia promedio (50 mejores): 33.10 Distancia promedio (todos): 49.56
Comparaciones: Wall-E en Diferentes Condiciones¶
Evaluamos el rendimiento de los algoritmos con el robot Wall-E bajo diversas transformaciones: rotación, escala, oclusión parcial y cambios de entorno.
# Wall-E: Mismo entorno, diferentes ángulos
comparator.compare_algorithms(
'data/01_Wall-E_Front_Environment1.jpg',
'data/02_Wall-E_Right_Side_Environment1.jpg'
)
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 02_Wall-E_Right_Side_Environment1.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=6978 Total matches: 618 Distancia promedio (50 mejores): 22.80 Distancia promedio (todos): 48.06 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=1290 Total matches: 390 Distancia promedio (50 mejores): 133.08 Distancia promedio (todos): 275.55 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 256 Distancia promedio (50 mejores): 39.00 Distancia promedio (todos): 52.80
{'harris': {'keypoints': (7251, 6978),
'matches': 618,
'avg_dist_50': 22.8,
'avg_dist_all': 48.06310679611651},
'sift': {'keypoints': (1389, 1290),
'matches': 390,
'avg_dist_50': 133.08342834472657,
'avg_dist_all': 275.55492819761616},
'orb': {'keypoints': (1000, 1000),
'matches': 256,
'avg_dist_50': 39.0,
'avg_dist_all': 52.80078125}}
# Wall-E: Rotación extrema (upside down)
comparator.compare_algorithms(
'data/01_Wall-E_Front_Environment1.jpg',
'data/08_Wall-E_Upside_Down_Environment2.jpg'
)
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 08_Wall-E_Upside_Down_Environment2.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=25636 Total matches: 1135 Distancia promedio (50 mejores): 24.44 Distancia promedio (todos): 47.06 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=4724 Total matches: 622 Distancia promedio (50 mejores): 108.51 Distancia promedio (todos): 258.08 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 246 Distancia promedio (50 mejores): 42.46 Distancia promedio (todos): 56.77
{'harris': {'keypoints': (7251, 25636),
'matches': 1135,
'avg_dist_50': 24.44,
'avg_dist_all': 47.05903083700441},
'sift': {'keypoints': (1389, 4724),
'matches': 622,
'avg_dist_50': 108.50702880859374,
'avg_dist_all': 258.07929614885825},
'orb': {'keypoints': (1000, 1000),
'matches': 246,
'avg_dist_50': 42.46,
'avg_dist_all': 56.77235772357724}}
# Wall-E: Cambio de escala (zoom out)
comparator.compare_algorithms(
'data/01_Wall-E_Front_Environment1.jpg',
'data/05_Wall-E_Zoom_Out_Up_Environment2.jpg'
)
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 05_Wall-E_Zoom_Out_Up_Environment2.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=99433 Total matches: 1647 Distancia promedio (50 mejores): 25.32 Distancia promedio (todos): 49.22 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=10632 Total matches: 742 Distancia promedio (50 mejores): 169.28 Distancia promedio (todos): 283.92 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 239 Distancia promedio (50 mejores): 52.40 Distancia promedio (todos): 64.68
{'harris': {'keypoints': (7251, 99433),
'matches': 1647,
'avg_dist_50': 25.32,
'avg_dist_all': 49.22465088038859},
'sift': {'keypoints': (1389, 10632),
'matches': 742,
'avg_dist_50': 169.28126541137695,
'avg_dist_all': 283.91926306341537},
'orb': {'keypoints': (1000, 1000),
'matches': 239,
'avg_dist_50': 52.4,
'avg_dist_all': 64.68200836820084}}
# Wall-E: Oclusión parcial con objetos
comparator.compare_algorithms(
'data/01_Wall-E_Front_Environment1.jpg',
'data/12_Wall-E_Laydown_withObjects_Environment1.jpg'
)
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 12_Wall-E_Laydown_withObjects_Environment1.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=29901 Total matches: 1124 Distancia promedio (50 mejores): 22.68 Distancia promedio (todos): 45.84 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=3384 Total matches: 526 Distancia promedio (50 mejores): 95.13 Distancia promedio (todos): 253.26 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 233 Distancia promedio (50 mejores): 43.80 Distancia promedio (todos): 57.51
{'harris': {'keypoints': (7251, 29901),
'matches': 1124,
'avg_dist_50': 22.68,
'avg_dist_all': 45.844306049822066},
'sift': {'keypoints': (1389, 3384),
'matches': 526,
'avg_dist_50': 95.12756065368653,
'avg_dist_all': 253.2586680002539},
'orb': {'keypoints': (1000, 1000),
'matches': 233,
'avg_dist_50': 43.8,
'avg_dist_all': 57.506437768240346}}
# Wall-E: Con otros objetos en la escena
comparator.compare_algorithms(
'data/01_Wall-E_Front_Environment1.jpg',
'data/09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg'
)
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=7799 Total matches: 645 Distancia promedio (50 mejores): 23.98 Distancia promedio (todos): 43.46 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=1129 Total matches: 381 Distancia promedio (50 mejores): 123.00 Distancia promedio (todos): 254.66 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 241 Distancia promedio (50 mejores): 36.72 Distancia promedio (todos): 52.04
{'harris': {'keypoints': (7251, 7799),
'matches': 645,
'avg_dist_50': 23.98,
'avg_dist_all': 43.46201550387597},
'sift': {'keypoints': (1389, 1129),
'matches': 381,
'avg_dist_50': 122.9985629272461,
'avg_dist_all': 254.66115708238496},
'orb': {'keypoints': (1000, 1000),
'matches': 241,
'avg_dist_50': 36.72,
'avg_dist_all': 52.04149377593361}}
# Wall-E: Cambio de entorno completo
comparator.compare_algorithms(
'data/01_Wall-E_Front_Environment1.jpg',
'data/13_Wall-E_Front_Outside_Environment3.jpg'
)
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 13_Wall-E_Front_Outside_Environment3.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=116800 Total matches: 1782 Distancia promedio (50 mejores): 24.30 Distancia promedio (todos): 47.32 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=14589 Total matches: 748 Distancia promedio (50 mejores): 99.33 Distancia promedio (todos): 268.20 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 242 Distancia promedio (50 mejores): 35.96 Distancia promedio (todos): 55.79
{'harris': {'keypoints': (7251, 116800),
'matches': 1782,
'avg_dist_50': 24.3,
'avg_dist_all': 47.32435465768799},
'sift': {'keypoints': (1389, 14589),
'matches': 748,
'avg_dist_50': 99.3261750793457,
'avg_dist_all': 268.1964029220336},
'orb': {'keypoints': (1000, 1000),
'matches': 242,
'avg_dist_50': 35.96,
'avg_dist_all': 55.789256198347104}}
Comparación: Puzzle de Popeye¶
# Puzzle: Diferentes vistas y oclusiones
comparator.compare_algorithms(
'data/15_PopeyePuzzle_Front.jpg',
'data/16_PopeyePuzzle_MorePuzzles.jpg'
)
============================================================ 📊 Resultados para: 15_PopeyePuzzle_Front.jpg vs 16_PopeyePuzzle_MorePuzzles.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=16584, Img2=48348 Total matches: 2408 Distancia promedio (50 mejores): 18.68 Distancia promedio (todos): 47.08 🔹 SIFT: Keypoints detectados: Img1=2450, Img2=4805 Total matches: 1100 Distancia promedio (50 mejores): 51.09 Distancia promedio (todos): 212.60 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 287 Distancia promedio (50 mejores): 32.24 Distancia promedio (todos): 57.02
{'harris': {'keypoints': (16584, 48348),
'matches': 2408,
'avg_dist_50': 18.68,
'avg_dist_all': 47.08056478405316},
'sift': {'keypoints': (2450, 4805),
'matches': 1100,
'avg_dist_50': 51.09270275115967,
'avg_dist_all': 212.59723248741844},
'orb': {'keypoints': (1000, 1000),
'matches': 287,
'avg_dist_50': 32.24,
'avg_dist_all': 57.01742160278746}}
Comparaciones Adicionales: Wall-E¶
# Referencia base
base_image = 'data/01_Wall-E_Front_Environment1.jpg'
# Lista de imágenes para comparar
comparison_images = [
'data/03_Wall-E_Right_Side_Environment2.jpg',
'data/04_Wall-E_Left_Side_Environment2.jpg',
'data/06_Wall-E_Close_Up_Front_Environment2.jpg',
'data/07_Wall-E_Laydown_Environment2.jpg',
'data/10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg',
'data/11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg',
'data/14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg'
]
# Ejecutar comparaciones
for img_path in comparison_images:
comparator.compare_algorithms(base_image, img_path)
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 03_Wall-E_Right_Side_Environment2.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=40962 Total matches: 1342 Distancia promedio (50 mejores): 26.14 Distancia promedio (todos): 46.79 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=6820 Total matches: 656 Distancia promedio (50 mejores): 145.63 Distancia promedio (todos): 274.80 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 233 Distancia promedio (50 mejores): 45.68 Distancia promedio (todos): 57.71
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 04_Wall-E_Left_Side_Environment2.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=37241 Total matches: 1326 Distancia promedio (50 mejores): 21.48 Distancia promedio (todos): 45.71 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=6600 Total matches: 669 Distancia promedio (50 mejores): 135.13 Distancia promedio (todos): 273.40 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 238 Distancia promedio (50 mejores): 46.50 Distancia promedio (todos): 59.34
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 06_Wall-E_Close_Up_Front_Environment2.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=30187 Total matches: 1182 Distancia promedio (50 mejores): 21.56 Distancia promedio (todos): 45.18 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=5351 Total matches: 683 Distancia promedio (50 mejores): 119.29 Distancia promedio (todos): 264.28 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 263 Distancia promedio (50 mejores): 35.78 Distancia promedio (todos): 53.68
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 07_Wall-E_Laydown_Environment2.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=27800 Total matches: 1179 Distancia promedio (50 mejores): 19.58 Distancia promedio (todos): 45.48 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=9459 Total matches: 750 Distancia promedio (50 mejores): 113.21 Distancia promedio (todos): 268.15 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 259 Distancia promedio (50 mejores): 42.06 Distancia promedio (todos): 57.88
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=5745 Total matches: 508 Distancia promedio (50 mejores): 21.48 Distancia promedio (todos): 43.71 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=937 Total matches: 411 Distancia promedio (50 mejores): 90.24 Distancia promedio (todos): 219.69 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 279 Distancia promedio (50 mejores): 29.14 Distancia promedio (todos): 47.01
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=3706 Total matches: 381 Distancia promedio (50 mejores): 26.10 Distancia promedio (todos): 48.46 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=961 Total matches: 298 Distancia promedio (50 mejores): 154.76 Distancia promedio (todos): 267.39 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 236 Distancia promedio (50 mejores): 43.44 Distancia promedio (todos): 55.72
============================================================ 📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg ============================================================ 🔹 HARRIS: Keypoints detectados: Img1=7251, Img2=39092 Total matches: 1272 Distancia promedio (50 mejores): 25.82 Distancia promedio (todos): 49.05 🔹 SIFT: Keypoints detectados: Img1=1389, Img2=6663 Total matches: 637 Distancia promedio (50 mejores): 163.92 Distancia promedio (todos): 281.75 🔹 ORB: Keypoints detectados: Img1=1000, Img2=1000 Total matches: 237 Distancia promedio (50 mejores): 49.76 Distancia promedio (todos): 61.42
Visualización de Resultados Agregados¶
def plot_algorithm_comparison(metrics: dict) -> None:
"""
Genera gráficos de líneas comparando el rendimiento de los algoritmos.
"""
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 12))
x_labels = metrics['image_pairs']
x_pos = range(len(x_labels))
# Gráfico 1: Top 50 matches
ax1.plot(x_pos, metrics['sift_50'], marker='o', linewidth=2,
label='SIFT (50 mejores)', color='#3498db')
ax1.plot(x_pos, metrics['orb_50'], marker='s', linewidth=2,
label='ORB (50 mejores)', color='#2ecc71')
ax1.plot(x_pos, metrics['harris_50'], marker='^', linewidth=2,
label='Harris (50 mejores)', color='#e74c3c')
ax1.set_xticks(x_pos)
ax1.set_xticklabels(x_labels, rotation=45, ha='right', fontsize=8)
ax1.set_ylabel('Distancia Promedio', fontsize=11, fontweight='bold')
ax1.set_title('Comparación: Distancia Promedio de los 50 Mejores Matches',
fontsize=13, fontweight='bold')
ax1.legend(loc='best', fontsize=10)
ax1.grid(True, alpha=0.3)
# Gráfico 2: Todos los matches
ax2.plot(x_pos, metrics['sift_all'], marker='o', linewidth=2,
label='SIFT (todos)', color='#1a5490')
ax2.plot(x_pos, metrics['orb_all'], marker='s', linewidth=2,
label='ORB (todos)', color='#1d8348')
ax2.plot(x_pos, metrics['harris_all'], marker='^', linewidth=2,
label='Harris (todos)', color='#943126')
ax2.set_xticks(x_pos)
ax2.set_xticklabels(x_labels, rotation=45, ha='right', fontsize=8)
ax2.set_ylabel('Distancia Promedio', fontsize=11, fontweight='bold')
ax2.set_title('Comparación: Distancia Promedio de Todos los Matches',
fontsize=13, fontweight='bold')
ax2.legend(loc='best', fontsize=10)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Generar gráfico comparativo
plot_algorithm_comparison(comparator.metrics)
def plot_summary_statistics(metrics: dict) -> None:
"""
Genera gráfico de barras con promedios generales.
"""
labels = ['SIFT\n(50)', 'SIFT\n(Todos)', 'ORB\n(50)',
'ORB\n(Todos)', 'Harris\n(50)', 'Harris\n(Todos)']
averages = [
np.mean(metrics['sift_50']),
np.mean(metrics['sift_all']),
np.mean(metrics['orb_50']),
np.mean(metrics['orb_all']),
np.mean(metrics['harris_50']),
np.mean(metrics['harris_all'])
]
colors = ['#3498db', '#1a5490', '#2ecc71', '#1d8348', '#e74c3c', '#943126']
plt.figure(figsize=(12, 7))
bars = plt.bar(labels, averages, color=colors, edgecolor='black', linewidth=1.2)
plt.ylabel('Distancia Promedio', fontsize=12, fontweight='bold')
plt.title('Resumen: Distancia Promedio por Algoritmo (Todas las Comparaciones)',
fontsize=14, fontweight='bold', pad=20)
plt.ylim(0, max(averages) * 1.2)
plt.grid(axis='y', alpha=0.3)
# Añadir valores sobre las barras
for bar, value in zip(bars, averages):
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height,
f'{value:.2f}',
ha='center', va='bottom', fontsize=11, fontweight='bold')
plt.tight_layout()
plt.show()
plot_summary_statistics(comparator.metrics)
Tabla Comparativa Detallada¶
import pandas as pd
def create_comparison_table(metrics: dict) -> pd.DataFrame:
"""
Crea tabla comparativa con todas las métricas.
"""
df = pd.DataFrame({
'Par de Imágenes': [pair.replace('\n', ' ') for pair in metrics['image_pairs']],
'KP SIFT': metrics['keypoints_sift'],
'KP ORB': metrics['keypoints_orb'],
'KP Harris': metrics['keypoints_harris'],
'Matches SIFT': metrics['matches_sift'],
'Matches ORB': metrics['matches_orb'],
'Matches Harris': metrics['matches_harris'],
'SIFT Dist (50)': [f"{x:.2f}" for x in metrics['sift_50']],
'ORB Dist (50)': [f"{x:.2f}" for x in metrics['orb_50']],
'Harris Dist (50)': [f"{x:.2f}" for x in metrics['harris_50']]
})
return df
# Crear y mostrar tabla
results_table = create_comparison_table(comparator.metrics)
print("\n" + "="*120)
print("📊 TABLA COMPARATIVA DE RESULTADOS")
print("="*120)
print(results_table.to_string(index=False))
print("="*120)
========================================================================================================================
📊 TABLA COMPARATIVA DE RESULTADOS
========================================================================================================================
Par de Imágenes KP SIFT KP ORB KP Harris Matches SIFT Matches ORB Matches Harris SIFT Dist (50) ORB Dist (50) Harris Dist (50)
elon_1 vs elon_2 296 967 1125 102 311 152 204.85 33.10 17.26
01_Wall-E_Front vs 02_Wall-E_Right 1389 1000 7251 390 256 618 133.08 39.00 22.80
01_Wall-E_Front vs 08_Wall-E_Upsid 1389 1000 7251 622 246 1135 108.51 42.46 24.44
01_Wall-E_Front vs 05_Wall-E_Zoom_ 1389 1000 7251 742 239 1647 169.28 52.40 25.32
01_Wall-E_Front vs 12_Wall-E_Laydo 1389 1000 7251 526 233 1124 95.13 43.80 22.68
01_Wall-E_Front vs 09_Wall-E_And_H 1389 1000 7251 381 241 645 123.00 36.72 23.98
01_Wall-E_Front vs 13_Wall-E_Front 1389 1000 7251 748 242 1782 99.33 35.96 24.30
15_PopeyePuzzle vs 16_PopeyePuzzle 2450 1000 16584 1100 287 2408 51.09 32.24 18.68
01_Wall-E_Front vs 03_Wall-E_Right 1389 1000 7251 656 233 1342 145.63 45.68 26.14
01_Wall-E_Front vs 04_Wall-E_Left_ 1389 1000 7251 669 238 1326 135.13 46.50 21.48
01_Wall-E_Front vs 06_Wall-E_Close 1389 1000 7251 683 263 1182 119.29 35.78 21.56
01_Wall-E_Front vs 07_Wall-E_Laydo 1389 1000 7251 750 259 1179 113.21 42.06 19.58
01_Wall-E_Front vs 10_Wall-E_And_H 1389 1000 7251 411 279 508 90.24 29.14 21.48
01_Wall-E_Front vs 11_Wall-E_And_H 1389 1000 7251 298 236 381 154.76 43.44 26.10
01_Wall-E_Front vs 14_Wall-E_Front 1389 1000 7251 637 237 1272 163.92 49.76 25.82
========================================================================================================================
Estadísticas Agregadas¶
def print_summary_statistics(metrics: dict) -> None:
"""
Imprime estadísticas resumidas de todos los experimentos.
"""
print("\n" + "="*80)
print("📈 ESTADÍSTICAS RESUMIDAS")
print("="*80)
algorithms = [
('SIFT', 'sift_50', 'sift_all'),
('ORB', 'orb_50', 'orb_all'),
('Harris', 'harris_50', 'harris_all')
]
for name, key_50, key_all in algorithms:
print(f"\n🔹 {name}:")
print(f" Distancia promedio (50 mejores matches):")
print(f" Media: {np.mean(metrics[key_50]):.2f} | "
f"Desv. Std: {np.std(metrics[key_50]):.2f} | "
f"Min: {np.min(metrics[key_50]):.2f} | "
f"Max: {np.max(metrics[key_50]):.2f}")
print(f" Distancia promedio (todos los matches):")
print(f" Media: {np.mean(metrics[key_all]):.2f} | "
f"Desv. Std: {np.std(metrics[key_all]):.2f} | "
f"Min: {np.min(metrics[key_all]):.2f} | "
f"Max: {np.max(metrics[key_all]):.2f}")
print("\n" + "="*80)
print_summary_statistics(comparator.metrics)
================================================================================
📈 ESTADÍSTICAS RESUMIDAS
================================================================================
🔹 SIFT:
Distancia promedio (50 mejores matches):
Media: 127.10 | Desv. Std: 36.45 | Min: 51.09 | Max: 204.85
Distancia promedio (todos los matches):
Media: 261.26 | Desv. Std: 19.71 | Min: 212.60 | Max: 283.92
🔹 ORB:
Distancia promedio (50 mejores matches):
Media: 40.54 | Desv. Std: 6.47 | Min: 29.14 | Max: 52.40
Distancia promedio (todos los matches):
Media: 55.93 | Desv. Std: 4.32 | Min: 47.01 | Max: 64.68
🔹 Harris:
Distancia promedio (50 mejores matches):
Media: 22.77 | Desv. Std: 2.67 | Min: 17.26 | Max: 26.14
Distancia promedio (todos los matches):
Media: 45.40 | Desv. Std: 4.80 | Min: 28.60 | Max: 49.22
================================================================================
Hallazgos y Análisis¶
Observaciones Principales:¶
1. SIFT - El Más Robusto
- ✅ Mayor número de keypoints detectados
- ✅ Excelente invarianza a escala y rotación
- ✅ Distancias de matching consistentemente bajas
- ❌ Mayor costo computacional
2. ORB - El Más Eficiente
- ✅ Muy rápido (descriptores binarios)
- ✅ Buen rendimiento en transformaciones moderadas
- ❌ Sensible a cambios de escala extremos
- ✅ Ideal para aplicaciones en tiempo real
3. Harris + ORB Descriptors
- ✅ Excelente localización de esquinas
- ✅ Keypoints muy estables
- ❌ No invariante a escala
- ❌ Puede fallar con transformaciones severas
Conclusiones sobre Matching:¶
Las pruebas revelan que:
- SIFT es superior cuando se requiere máxima precisión y robustez
- ORB es la mejor opción para aplicaciones con restricciones de tiempo
- Harris funciona bien en escenas con esquinas prominentes pero requiere condiciones controladas
- La elección del algoritmo debe balancear precisión, velocidad y recursos computacionales disponibles
12. Conclusiones Personales ¶
🎓 Javier Augusto Rebull Saucedo (A01795838)¶
Esta actividad me permitió comprender profundamente cómo los algoritmos de feature matching son fundamentales en visión computacional. Fue particularmente revelador observar cómo SIFT, a pesar de su mayor costo computacional, proporciona resultados superiores en términos de robustez ante transformaciones geométricas. La implementación práctica me hizo valorar el trade-off entre precisión y eficiencia: mientras que SIFT ofrece la mejor calidad de matching, ORB demuestra ser más que suficiente para muchas aplicaciones del mundo real donde la velocidad es crítica.
El uso de principios de Clean Code durante la implementación no solo mejoró la legibilidad del código, sino que facilitó enormemente el proceso de debugging y la expansión del análisis comparativo. Aprendí que en proyectos de ML/CV, una arquitectura de código bien diseñada es tan importante como el algoritmo elegido.
🎓 Juan Carlos Pérez Nava (A01795941)¶
La experimentación con distintos escenarios (rotaciones extremas, oclusiones, cambios de iluminación) me proporcionó una comprensión práctica de las limitaciones y fortalezas de cada algoritmo que va más allá de la teoría. Destaco especialmente cómo Harris Corner Detection, aunque simple en concepto, ofrece una base sólida cuando se combina con descriptores modernos.
Un insight importante fue comprender que la elección del algoritmo no es universal: depende del contexto de aplicación. Para sistemas de navegación de robots que requieren procesamiento en tiempo real, ORB es claramente superior. Sin embargo, para aplicaciones de reconstrucción 3D donde la precisión es primordial, SIFT justifica su mayor costo computacional. Esta actividad reforzó mi habilidad para tomar decisiones de diseño fundamentadas basadas en requisitos específicos del proyecto.
🎓 Luis Gerardo Sánchez Salazar (A01232963)¶
El análisis comparativo cuantitativo mediante métricas de distancia promedio fue particularmente valioso para evaluar objetivamente el rendimiento de cada algoritmo. Ver cómo las distancias de matching varían según las transformaciones aplicadas me ayudó a desarrollar intuición sobre cuándo un algoritmo es apropiado.
La implementación de la clase FeatureMatchingComparator aplicando principios de POO y Clean Code demostró el valor de abstraer funcionalidad común en métodos reutilizables. Esto no solo redujo duplicación de código, sino que hizo el análisis escalable a nuevas imágenes y algoritmos. Aprendí que en proyectos de investigación aplicada, la calidad del código experimental es tan importante como en desarrollo de producción, ya que facilita reproducibilidad y extensión del trabajo.
🎓 Oscar Enrique García García (A01016093)¶
Esta actividad consolidó mi comprensión de los fundamentos matemáticos detrás de cada algoritmo. Entender cómo el Laplacian of Gaussian en SIFT proporciona invarianza a escala, o cómo la función de cornerness en Harris identifica puntos de interés, enriqueció significativamente mi conocimiento teórico.
La visualización de resultados mediante gráficos y tablas comparativas fue esencial para comunicar hallazgos de manera efectiva. Aprendí que en ciencia de datos y ML, la capacidad de presentar resultados de forma clara y visualmente atractiva es tan importante como generar esos resultados. Las herramientas de visualización (matplotlib, seaborn) son fundamentales para el análisis exploratorio y la comunicación de insights a audiencias técnicas y no técnicas.
13. Reflexión Final del Equipo ¶
🚀 Team 13 - Reflexión Colectiva¶
Este proyecto representó una valiosa oportunidad para integrar conocimientos teóricos de visión computacional con implementación práctica y buenas prácticas de ingeniería de software. Como equipo, logramos no solo comparar algoritmos de feature matching, sino establecer un framework metodológico riguroso para evaluación experimental.
Logros Destacados:¶
Implementación Robusta: Desarrollamos código limpio, modular y bien documentado siguiendo principios de Clean Code, lo que facilitó la colaboración y la extensibilidad del proyecto.
Análisis Integral: Realizamos comparaciones exhaustivas en múltiples escenarios realistas, incluyendo transformaciones de escala, rotación, oclusión y cambios de entorno.
Visualización Efectiva: Creamos representaciones gráficas claras que facilitan la interpretación de resultados y la comunicación de insights.
Comprensión Profunda: Cada miembro del equipo desarrolló intuición sobre cuándo aplicar cada algoritmo basándose en requisitos específicos de aplicación.
Aprendizajes Clave:¶
No existe un "mejor" algoritmo universal: La elección óptima depende del contexto, requisitos de velocidad, recursos computacionales y nivel de transformaciones esperado.
Balance precisión-eficiencia: SIFT ofrece la mayor robustez pero a costa de tiempo de procesamiento; ORB es rápido pero menos robusto ante transformaciones extremas; Harris es simple pero efectivo en casos específicos.
Importancia del código de calidad: Aplicar Clean Code en proyectos de ML/CV no es opcional sino esencial para reproducibilidad, mantenibilidad y escalabilidad.
Valor de la experimentación: Las observaciones empíricas son fundamentales para validar teoría y desarrollar intuición práctica.
Aplicaciones Potenciales:¶
Los conocimientos adquiridos son directamente aplicables a:
- Sistemas de navegación autónoma (robots, drones)
- Realidad aumentada (tracking de objetos)
- Fotogrametría y reconstrucción 3D
- Sistemas de reconocimiento visual
- Aplicaciones de image stitching (panoramas)
Reflexión sobre Trabajo en Equipo:¶
La colaboración fue fundamental para el éxito del proyecto. La división de tareas, comunicación constante y revisión de código entre pares nos permitió detectar errores tempranamente y aprender de las diferentes aproximaciones de cada miembro. Este proyecto reforzó nuestra capacidad para trabajar efectivamente en equipo en proyectos técnicos complejos.
Perspectivas Futuras:¶
Este trabajo sienta las bases para explorar:
- Algoritmos de feature matching basados en Deep Learning (SuperPoint, D2-Net)
- Técnicas avanzadas de RANSAC para estimación robusta de transformaciones
- Implementaciones optimizadas para ejecución en dispositivos embebidos
- Aplicación de estos conceptos en proyectos capstone de la maestría
📚 Referencias¶
- Lowe, D. G. (2004). Distinctive Image Features from Scale-Invariant Keypoints. International Journal of Computer Vision.
- Rublee, E., Rabaud, V., Konolige, K., & Bradski, G. (2011). ORB: An efficient alternative to SIFT or SURF. IEEE International Conference on Computer Vision (ICCV).
- Harris, C., & Stephens, M. (1988). A Combined Corner and Edge Detector. Alvey Vision Conference.
- OpenCV Documentation: https://docs.opencv.org/
- Módulo 3.2 - Extracción de Descriptores (Dr. Ochoa)
Team 13 | Maestría en Inteligencia Artificial Aplicada | Tec de Monterrey | 2025